package com.spc.car.web.permission;

import com.alibaba.fastjson.JSON;
import com.spc.car.common.utils.StringUtils;
import com.spc.car.core.framework.security.LoginUser;
import com.spc.car.data.domain.project.po.ProjectDocPO;
import com.spc.car.data.domain.project.po.ProjectTeamAddBatchPO;
import com.spc.car.data.domain.project.po.ProjectTeamPO;
import com.spc.car.data.domain.project.po.ProjectTeamUpdatePO;
import com.spc.car.data.entity.project.*;
import com.spc.car.data.entity.sys.SysRole;
import com.spc.car.data.service.project.ProjectDocService;
import com.spc.car.data.service.project.ProjectTeamService;
import com.spc.car.data.service.project.ProjectTenderingSectionService;
import com.spc.car.data.service.project.ProjectTenderingService;
import com.spc.car.service.project.BidProjectTeamService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Configuration
public class TeamPermissionEvaluator implements PermissionEvaluator {

    @Autowired
    private BidProjectTeamService bidProjectTeamService;

    @Autowired
    private ProjectTeamService projectTeamService;

    @Autowired
    private ProjectTenderingService projectTenderingService;

    @Autowired
    private ProjectDocService projectDocService;

    @Autowired
    private ProjectTenderingSectionService sectionService;

    @Override
    public boolean hasPermission(Authentication authentication, Object obj, Object permission) {
        log.info("{} - {} - {}", authentication.getPrincipal(), obj, permission);

        if (Objects.isNull(permission) || (permission instanceof String &&
                StringUtils.isBlank((String)permission)) ) {
            return true;
        }

        String userAccount = ((LoginUser)authentication.getPrincipal()).getUsername();

        if (Objects.nonNull(obj)
                && obj.getClass().isAssignableFrom(ProjectTeamAddBatchPO.class)) {
            return checkTeamSaveBatch((ProjectTeamAddBatchPO) obj,userAccount, (String)permission);
        }

        if (Objects.nonNull(obj)
                && obj.getClass().isAssignableFrom(ProjectTeamUpdatePO.class)) {
            return checkTeamUpdate((ProjectTeamUpdatePO)obj, userAccount, (String)permission);
        }

        if (Objects.nonNull(obj)
                && obj.getClass().isAssignableFrom(ProjectDocPO.class)) {
            return checkProjectDoc((ProjectDocPO)obj, userAccount, (String)permission);
        }

        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        log.info("{} > {} > {} > {}", authentication.getPrincipal(), targetId, targetType, permission);

        if (Objects.isNull(permission) || (permission instanceof String &&
                StringUtils.isBlank((String)permission)) ) {
            return true;
        }

        String userAccount = ((LoginUser)authentication.getPrincipal()).getUsername();
        String projectTenderingCode = Strings.EMPTY;

        if (hasAnyAuthority(authentication, (String)permission)) {
            return true;
        }

        if ("memberId".equals(targetType) && targetId instanceof Long) {
            ProjectTeam projectTeam = projectTeamService.getById(targetId);
            if (Objects.isNull(projectTeam)) {
                return false;
            }
            projectTenderingCode = projectTeam.getProjectTenderingCode();
        } else if ("projectTenderingCode".equals(targetType)
                && targetId instanceof String) {
            projectTenderingCode = (String)targetId;
        } else if ("projectTenderingId".equals(targetType)) {
            if (Objects.isNull(targetId)) {
                return true;
            }

            ProjectTendering projectTendering = projectTenderingService.getById(targetId);
            if (Objects.isNull(projectTendering)) {
                return false;
            }
            projectTenderingCode = projectTendering.getCode();
        } else if ("docId".equals(targetType)
                && targetId instanceof Long) {
            ProjectDoc projectDoc = projectDocService.getById(targetId);

            if (Objects.isNull(projectDoc)
                    || ("D".equals(projectDoc.getCategory())
                        && StringUtils.isBlank(projectDoc.getProjectTenderingCode()) )) {
                return false;
            }

            if ("T".equals(projectDoc.getCategory())) {
                return true;
            }
            projectTenderingCode = projectDoc.getProjectTenderingCode();
        } else if ("sectionId".equals(targetType)
                && targetId instanceof Long) {
            ProjectTenderingSection section = sectionService.getById(targetId);
            if (Objects.isNull(section)) {
                return false;
            }
            projectTenderingCode = section.getProjectTenderingCode();
        } else if ("uploadFileParams".equals(targetType)
                && targetId instanceof String) {
            String params = (String) targetId;

            if (StringUtils.isBlank(params)) {
                return true;
            }

            Map<String, Object> paramMaps = new HashMap<>();
            if (org.apache.commons.lang3.StringUtils.isNotBlank(params)) {
                paramMaps = JSON.parseObject(params, Map.class);
            }
            if (MapUtils.isEmpty(paramMaps)
                    || StringUtils.isBlank((String)paramMaps.get("projectCode"))
                    || !((String)paramMaps.get("projectCode")).startsWith("PT")) {
                return true;
            }
            projectTenderingCode = (String)paramMaps.get("projectCode");
        }


        if (StringUtils.isBlank(projectTenderingCode)) {
            return false;
        }

        return bidProjectTeamService.havePermission(projectTenderingCode,
                userAccount, (String)permission);
    }

    private boolean checkTeamSaveBatch(ProjectTeamAddBatchPO memberBatchPO,
                                       String userAccount, String permission) {

        List<Integer> roleIds = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(memberBatchPO.getMemberList())) {
            roleIds = memberBatchPO.getMemberList().stream()
                    .map(ProjectTeamPO::getProjectRoleId)
                    .distinct()
                    .collect(Collectors.toList());
        }
        return checkRole(memberBatchPO.getProjectTenderingCode(),
                userAccount, permission, roleIds);
    }

    private boolean checkTeamUpdate(ProjectTeamUpdatePO projectTeamUpdatePO,
                                    String userAccount, String permission) {

        ProjectTeam projectTeam = projectTeamService.getById(projectTeamUpdatePO.getId());
        if (Objects.isNull(projectTeam)
                || StringUtils.isBlank(projectTeam.getProjectTenderingCode())) {
            return false;
        }

        List<Integer> roleIds = Arrays.asList(projectTeamUpdatePO.getProjectRoleId());
        return checkRole(projectTeam.getProjectTenderingCode(),
                userAccount, permission, roleIds);
    }

    private boolean checkProjectDoc(ProjectDocPO projectDocPO,
                                    String userAccount, String permission) {
        String projectTenderingCode = Strings.EMPTY;

        if (Objects.nonNull(projectDocPO.getId())) {
            ProjectDoc projectDoc = projectDocService.getById(projectDocPO.getId());
            if (Objects.isNull(projectDoc)) {
                return false;
            }

            projectTenderingCode = projectDoc.getProjectTenderingCode();

            if (StringUtils.isNotBlank(projectDocPO.getProjectTenderingCode())
                    && !projectTenderingCode.equals(projectDocPO.getProjectTenderingCode())) {
                return false;
            }
        } else {
            projectTenderingCode = projectDocPO.getProjectTenderingCode();
        }

        if (StringUtils.isNotBlank(projectTenderingCode)) {
            if (bidProjectTeamService.isTeamMember(projectTenderingCode,
                    userAccount, permission)) {
                return true;
            }
        }
        return false;
    }

    private boolean checkRole(String projectTenderingCode, String userAccount,
                              String permission, List<Integer> roleIds) {
        if (!bidProjectTeamService.isTeamMember(projectTenderingCode,
                userAccount, permission)) {
            return false;
        }

        List<ProjectRole> suitRoles = bidProjectTeamService.listSuitRole(
                projectTenderingCode,
                userAccount
        );

        if (CollectionUtils.isEmpty(suitRoles)) {
            return false;
        }

        List<Integer> suitRoleIds = suitRoles.stream()
                .map(ProjectRole::getId)
                .distinct()
                .collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(roleIds)
                && !suitRoleIds.containsAll(roleIds)) {
            return false;
        }
        return true;
    }

    public List<String> listAuthorities(Authentication authentication) {
        return authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .distinct().collect(Collectors.toList());
    }

    public Set<String> listRoles(Authentication authentication) {
        if(Objects.isNull(authentication) || Objects.isNull(authentication.getAuthorities())) {
            return new HashSet<>();
        }
        return AuthorityUtils.authorityListToSet(authentication.getAuthorities());
    }

    public boolean hasAnyAuthority(Authentication authentication, String permissions) {
        return new MethodSecurityExpression(authentication)
                .hasAnyUserAuthority(permissions.split(","));
    }

    private class MethodSecurityExpression extends SecurityExpressionRoot {
        private final LoginUser user;

        MethodSecurityExpression(Authentication a) {
            super(a);
            this.user = (LoginUser)a.getPrincipal();
        }

        public boolean hasAnyUserRole(String... roles) {
            return hasAnyRole(roles) || containAnyRoles(roles);
        }

        public boolean hasAnyUserAuthority(String... authorities) {
            return hasAnyAuthority(authorities) || containAnyAuthorities(authorities);
        }

        public boolean containAnyRoles(String... roles) {
            if (Objects.isNull(user) || CollectionUtils.isEmpty(user.getRoles())) {
                return false;
            }

            if (ArrayUtils.isEmpty(roles)) {
                return true;
            }

            return CollectionUtils.containsAny(
                    user.getRoles().stream()
                            .map(SysRole::getRoleKey)
                            .collect(Collectors.toList()),
                    Arrays.asList(roles));
        }

        public boolean containAnyAuthorities(String... authorities) {
            if (Objects.isNull(user) || CollectionUtils.isEmpty(user.getAuthorities())) {
                return false;
            }

            if (ArrayUtils.isEmpty(authorities)) {
                return true;
            }

            return CollectionUtils.containsAny(
                            user.getAuthorities().stream()
                              .map(GrantedAuthority::getAuthority)
                              .collect(Collectors.toList()),
                            Arrays.asList(authorities))
                    ||
                    CollectionUtils.containsAny(
                            user.getPermissions(),
                            Arrays.asList(authorities)
                    );
        }
    }

}
